package cn.blogss.camera;

import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import cn.blogss.camera.databinding.ActivityCameraPreviewBinding;
import cn.blogss.camera.vm.CameraPreviewVM;

/**
 * Little Bei
 */
public class CameraPreviewActivity extends AppCompatActivity implements View.OnClickListener, Camera.PreviewCallback {
    private static final String TAG = CameraPreviewActivity.class.getSimpleName();
    private ActivityCameraPreviewBinding binding;
    private SurfaceHolder surfaceHolder;
    private Camera camera;
    private Camera.Parameters cameraParameters;
    private int cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
    private CameraPreviewVM vm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityCameraPreviewBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        initView();
        initData();
    }

    private void initView() {
        binding.btTakePic.setOnClickListener(this);
        binding.btStartPreview.setOnClickListener(this);
        binding.btStopPreview.setOnClickListener(this);
        binding.btSwitchCamera.setOnClickListener(this);
    }

    private void initData() {
        vm = new ViewModelProvider(this).get(CameraPreviewVM.class);
        cameraId = getDefaultCameraId();

        surfaceHolder = binding.svCamera.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                // Open camera.
                if(camera == null){
                    openCamera();
                }
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
                Log.i(TAG, "surfaceDestroyed.");
                releaseCamera();
            }
        });
    }

    private void openCamera() {
        try {
            if(!hasAvailableCamera()){
                Toast.makeText(this, "No available cameras.", Toast.LENGTH_LONG).show();
                return;
            }
            camera = Camera.open(cameraId);
            camera.setPreviewCallback(this);
            // Successful open, Setting camera parameters.
            setCameraParameters();

        } catch (Exception e){
            e.printStackTrace();
            Toast.makeText(this, "Failed to open camera, Pls check whether the USB camera is inserted. cameraId = " + cameraId, Toast.LENGTH_LONG).show();
        }
    }

    /**
     * Setting camera Parameters
     */
    private void setCameraParameters() {
        try {
            cameraParameters = camera.getParameters();
            cameraParameters.setPreviewFormat(ImageFormat.NV21);
            Size fitSize = getBestResolutionBasedOnView(binding.svCamera);
            cameraParameters.setPreviewSize(fitSize.width, fitSize.height);
            Camera.Size defaultSize = cameraParameters.getPreviewSize();
            Log.i(TAG, "Now camera preview size after set: " + defaultSize.width + "x" + defaultSize.height);
            camera.setParameters(cameraParameters);
        } catch (Exception e){
            e.printStackTrace();
            Toast.makeText(this, "Failed to set camera parameters.", Toast.LENGTH_LONG).show();
        }
    }

    /**
     * Get best preview size based on view.
     * @param displayView the preview view
     * @return {@link Size)
     */
    private Size getBestResolutionBasedOnView(View displayView){
        int viewWidth = displayView.getWidth();
        int viewHeight = displayView.getHeight();
        Log.i(TAG, "DisplayView size: " + viewWidth + "x" + viewHeight);
        List<Camera.Size> sizeList = cameraParameters.getSupportedPreviewSizes();
        Camera.Size defaultSize = cameraParameters.getPreviewSize();
        Camera.Size defaultPicSize = cameraParameters.getPictureSize();
        Log.i(TAG, "Default camera preview size: " + defaultSize.width + "x" + defaultSize.height);
        Log.i(TAG, "Default camera pic save size: " + defaultPicSize.width + "x" + defaultPicSize.height);
        Iterator<Camera.Size> iterator = sizeList.iterator();
        Log.i(TAG, "Resolution supported by the camera:\n");

        // Print the resolution supported by the device.
        while(iterator.hasNext()){
            Camera.Size size = iterator.next();
            Log.i(TAG, size.width + "x" + size.height + "\n");
        }

        Size biggestSize = null;
        Size fitSize = null;
        Size oneSideEqualSize = null;
        Size oneSideLessSize = null;
        for(Camera.Size size : sizeList){
            if(biggestSize == null || (size.width >= biggestSize.width && size.height >= biggestSize.height)){
                biggestSize = new Size(size.width, size.height);
            }
            if(size.width == viewWidth && size.height == viewHeight){
                fitSize = new Size(size.width, size.height);
            } else if(size.width == viewWidth || size.height == viewHeight){
                if(oneSideEqualSize == null){
                    oneSideEqualSize = new Size(size.width, size.height);
                }
            } else if(size.width < viewWidth || size.height < viewHeight){
                if(oneSideLessSize == null){
                    oneSideLessSize = new Size(size.width, size.height);
                }
            }
        }

        if(fitSize == null){
            fitSize = oneSideEqualSize;
        }
        if(fitSize == null){
            fitSize = oneSideLessSize;
        }
        if(fitSize == null){
            fitSize = biggestSize;
        }
        Log.i(TAG, "The best preview size is: " + fitSize.width + "x" + fitSize.height);
        return fitSize;
    }

    /**
     * Switch front and rear cameras.
     */
    private void switchCamera(){
        if(!hasAvailableCamera()){
            Toast.makeText(this, "Available cameras is 0.", Toast.LENGTH_LONG).show();
            return;
        }
        releaseCamera();
        cameraId = (cameraId + 1) % Camera.getNumberOfCameras();
        Log.i(TAG, "SwitchCamera, cameraId = " +cameraId);
        openCamera();
        startPreview();
    }

    private boolean hasAvailableCamera(){
        Log.i(TAG, "Available cameras is " + Camera.getNumberOfCameras());
        return Camera.getNumberOfCameras() > 0;
    }

    private void startPreview() {
        try {
            camera.setPreviewDisplay(surfaceHolder);
            // 调整相机预览方向
            adjustDisplayOrientation();
            camera.startPreview();
        } catch (Exception e) { // RuntimeException.
            e.printStackTrace();
            Log.i(TAG, "Failed start preview.");
        }
    }

    private void adjustDisplayOrientation() {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, cameraInfo);
        Log.i(TAG, "camera facing: " + cameraInfo.facing);

        // The Angle that has been rotated from the natural direction, may be 0, 90, 180, 270
        int rotation  = getWindowManager().getDefaultDisplay().getRotation();
        Log.i(TAG, "rotation = " + rotation);
        Log.i(TAG, "need to rotate angel = " + cameraInfo.orientation);
        int degree = 0;

        if(cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
            degree = (cameraInfo.orientation + rotation) % 360;
            degree = (360 - degree) % 360;
        }else{  // back-facing
            degree = (cameraInfo.orientation - rotation + 360) % 360;
        }
        camera.setDisplayOrientation(degree);
    }

    private void stopPreview() {
        try {
            camera.stopPreview();
        } catch (Exception e) { // RuntimeException.
            e.printStackTrace();
            Log.i(TAG, "Failed stop preview.");
        }
    }

    private void releaseCamera(){
        if(camera != null){
            try {
                camera.stopPreview();
                camera.setPreviewCallback(null);
                camera.release();
                camera = null;
            } catch (Exception e){
                Log.i(TAG, "Failed releaseCamera.");
            }
        }
    }

    /**
     * If device has 2 cameras, we open the front camera. Otherwise, open the back camera.
     */
    private int getDefaultCameraId() {
        int cameraNum = Camera.getNumberOfCameras();
        Log.i(TAG, "cameraNum: " + cameraNum);
        return cameraNum == 2 ? 1 : 0;
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if(id == R.id.bt_take_pic){ // Take picture.

        } else if(id == R.id.bt_start_preview){ // Start preview
            if(camera != null){
                startPreview();
            }
        } else if(id == R.id.bt_stop_preview){  // Stop preview.
            if(camera != null){
                stopPreview();
            }
        } else if(id == R.id.bt_switch_camera){
            switchCamera();
        }
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseCamera();
    }

    private static class Size {
        public int width;
        public int height;

        public Size(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }
}
